package club.jdiy.core.base;

import club.jdiy.core.JDiyContext;
import club.jdiy.core.base.domain.*;
import club.jdiy.core.ex.JDiyException;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.query.internal.QueryImpl;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.persistence.*;
import java.lang.reflect.ParameterizedType;
import java.util.List;

/**
 * JDiy Dao层的实现类 XxxDaoImpl 基类默认实现抽象类。
 * @param <T> Entity实体类
 * @param <ID> 实体类的主键类型
 *
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20210309
 */
public abstract class JDiyBaseDao<T extends JpaEntity<ID>, ID> extends RepositoryBasic<T, ID> {

    /**
     * 根据Query 查询返回单条结果.
     * <strong>注意：</strong>此方法不应当暴露给外部程序，因此务必须声明为protected的．
     *
     * @param query Query
     * @return T
     */
    protected T getOne(Query query) {
        //noinspection unchecked
        List<T> ls = query.setMaxResults(1).getResultList();
        return ls != null && ls.size() > 0 ? ls.get(0) : null;
    }

    //====================================================================================
    //todo  以下两个wrap方法，暂时未使用到

    /**
     * 查询并将结果List封包为自定义的VO类型. <br/>
     * 程序会根据query中select语句后面的查询列名调用目标VO相应setter方法，完成数据的自动封包.<br/>
     * 例如：<br/>
     * List&lt;UserVo&gt; ls = wrapList(dbo.createNativeQuery("select id, name, mobile_phone as tel from user_table where id>5"), UserVo.class);<br/>
     * 查询语名返回了三个列名： id, name, tel. 其中tel是使用as设置的别名。此时程序会自动调用UserVo的 setId, setName,  setTel封包为UserVo <br/><br/>
     * 又如： wrapList(dbo.createQuery("select o.id as id, o.name as name from User o"))
     *
     * @param query javax.persistence.Query查询对象 ．　支持NativeQuery(原生SQL查询)和 Query(JQL查询). <br/>
     *              <strong>特别注意：</strong>
     *              <br/>若采用JQL方式,必须显式的指定要返回的列和列别名(即必须使用 as xxx设置字段名）．如：<br/>
     *              wrapList(dbo.createQuery("select o.id as id, o.name as name from User o")) <br/>
     *              即select后面必须指定要查询出的字段，且必须加上as，否则报错.　而NativeQuery无此要求．
     * @param tc    目标VO类型的class
     * @param <C>   目标类型泛型。
     * @return List结果
     */
    @SuppressWarnings("unchecked")
    protected final <C> List<C> wrapList(Query query, Class<C> tc) {
        if (tc == getDomainClass()) return query.getResultList();
        else if (query instanceof NativeQueryImpl) return query.unwrap(NativeQueryImpl.class)
                .setResultTransformer(new Transformer(tc))
                .getResultList();
        else if (query instanceof QueryImpl) return query.unwrap(QueryImpl.class)
                .setResultTransformer(new Transformer(tc))
                .getResultList();
        throw new JDiyException("Wrong Query type of '" + query.getClass().getName() + "'");
    }

    /**
     * 查询并将结果List封包为自定义的VO类型. <br/>
     * 程序会根据query中select语句后面的查询列名调用目标VO相应setter方法，完成数据的自动封包.<br/>
     * 例如：<br/>
     * UserVo vo = wrapOne(dbo.createNativeQuery("select id, name, mobile_phone as tel from user_table where id>5"), UserVo.class);<br/>
     * 查询语名返回了三个列名： id, name, tel. 其中tel是使用as设置的别名。此时程序会自动调用UserVo的 setId, setName,  setTel封包为UserVo <br/><br/>
     * 又如： findOne(dbo.createQuery("select o.role.id as roleId, o.name as name from User o"))
     *
     * @param query javax.persistence.Query查询对象 ．　支持NativeQuery(原生SQL查询)和 Query(JQL查询). <br/>
     *              <strong>特别注意：</strong>
     *              <br/>若采用JQL方式,必须显式的指定要返回的列和列别名(即必须使用 as xxx设置字段名）．如：<br/>
     *              wrapList(dbo.createQuery("select o.id as id, o.name as name from User o")) <br/>
     *              即select后面必须指定要查询出的字段，且必须加上as，否则报错.　而NativeQuery无此要求．
     * @param tc    目标VO类型的class
     * @param <C>   目标类型泛型。
     * @return List结果
     */
    @SuppressWarnings("unused")
    protected final <C> C wrapOne(Query query, Class<C> tc) {
        List<C> ls = wrapList(query.setMaxResults(1), tc);
        return ls == null || ls.size() < 1 ? null : ls.get(0);
    }

    //====================================================================================
    @PostConstruct
    private void init() {
        super.init(JpaEntityInformationSupport.getEntityInformation(tClass, entityManager), entityManager);
    }

    @PersistenceContext
    protected EntityManager entityManager;
    @Resource
    @SuppressWarnings("unused")
    protected JPAQueryFactory queryFactory;
    @Resource
    protected JDiyContext context;

    @SuppressWarnings("unchecked")
    protected final Class<T> tClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];

}
